home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-04 / bipl.zip / PROGS.ZIP / ICALC.ICN < prev    next >
Text File  |  1992-09-28  |  13KB  |  472 lines

  1. ##############################################################################
  2. #
  3. #    File:     icalc.icn
  4. #
  5. #    Subject:  Program to simulate infix desk calculator
  6. #
  7. #    Author:   Steve Wampler
  8. #
  9. #    Date:     December 20, 1990
  10. #
  11. #############################################################################
  12. #
  13. # This is a simple infix calculator with control structures and
  14. #   compound statements.  It illustrates a technique that can be
  15. #   easily used in Icon to greatly reduce the performance cost
  16. #   associated with recursive-descent parsing with backtracking.
  17. #   There are numerous improvements and enhancements that can be
  18. #   made.
  19. #
  20. # Features include:
  21. #
  22. #    - integer and real value arithmetic
  23. #       - variables
  24. #       - function calls to Icon functions
  25. #       - strings allowed as function arguments
  26. #       - unary operators:
  27. #             +    (absolute value), - (negation)
  28. #       - assignment:
  29. #             :=
  30. #       - binary operators:
  31. #             +,-,*,/,%,^,
  32. #       - relational operators:
  33. #             =, !=, <, <=, >, >=
  34. #                (all return 1 for true and 0 for false)
  35. #       - compound statements in curly braces with semicolon separators
  36. #       - if-then and if-then-else
  37. #       - while-do
  38. #       - limited form of multiline input
  39. #
  40. # The grammar at the start of the 'parser' proper provides more
  41. #   details.
  42. #
  43. # Normally, the input is processed one line at a time, in calculator
  44. #   fashion.  However, compound statements can be continued across
  45. #   line boundaries.
  46. #
  47. # Examples:
  48. #
  49. #   Here is a simple input:
  50. #
  51. #       {
  52. #    a := 10;
  53. #    while a >= 0 do {
  54. #          write(a);
  55. #          a := a - 1
  56. #          };
  57. #       write("Blastoff")
  58. #       }
  59. #
  60. #    (execution is delayed until entire compound statement is entered)
  61. #
  62. #   Another one:
  63. #
  64. #   write(pi := 3.14159)
  65. #   write(sin(pi/2))
  66. #
  67. #    (execution done as each line is entered)
  68. #
  69. ##############################################################################
  70.  
  71.     # the types for parse tree nodes:
  72.  
  73. record  trinary(op,first,second,third)
  74. record  binop(op,left,right)
  75. record  unary(op,opnd)
  76. record  id(name)
  77. record  const(value)
  78.  
  79.     # a global table for holding variable values:
  80.  
  81. global  sym_tab
  82.  
  83.  
  84. procedure main()
  85.    local line, sline
  86.  
  87.    sym_tab := table()
  88.  
  89.    every line := getbs() do {            # a 'line' may be more
  90.                         #   than one input line
  91.       if *(sline := trim(line)) > 0 then {    # skip empty lines
  92.          process(parse(sline))
  93.          }
  94.       }
  95. end
  96.  
  97. ### Input routines...
  98.  
  99. ## getbs - read enough input to ensure that it is
  100. #    balanced with respect to curly braces, allowing
  101. #       compound statements to extend across lines...
  102. #    This can be made considerably more sophisticated,
  103. #       but handles the more common cases.
  104. #
  105. procedure getbs()
  106. static tmp
  107.    initial tmp := (("" ~== |read()) || " ") | fail
  108.  
  109.    repeat {
  110.       while not checkbal(tmp,'{','}') do {
  111.          if more('}','{',tmp) then break
  112.          tmp ||:= (("" ~== |read()) || " ") | break
  113.          }
  114.       suspend tmp
  115.       tmp := (("" ~== |read()) || " ") | fail
  116.       }
  117. end
  118.  
  119. ## checkbal(s) - quick check to see if s is
  120. #       balanced w.r.t. braces or parens
  121. #
  122. procedure checkbal(s,l,r)
  123.    return (s ? 1(tab(bal(&cset,l,r)),pos(-1)))
  124. end
  125.  
  126. ## more(c1,c2,s) - succeeds if any prefix of
  127. #       s has more characters in c1 than
  128. #       characters in c2, fails otherwise
  129. #
  130. procedure more(c1,c2,s)
  131. local cnt
  132.    cnt := 0
  133.    s ? while (cnt <= 0) & not pos(0) do {
  134.          (any(c1) & cnt +:= 1) |
  135.          (any(c2) & cnt -:= 1)
  136.          move(1)
  137.          }
  138.    return cnt >= 0
  139. end
  140.  
  141.  
  142. ### Parser routines...  Implementing an efficient recursive-descent
  143. ###     parser with backtracking.
  144.  
  145. #   Parser  --  Based on following CFG, but modified to
  146. #               avoid useless backtracking...  (see comments
  147. #           preceding procedures 'save' and 'restore')
  148.  
  149. #      Statement ::= Expr | If | While | Compound
  150. #
  151. #      Compound ::= {Statement_list}
  152. #
  153. #      Statement_list ::= Statement | Statement ; Statement_list
  154. #
  155. #      If ::= if Expr then Statement Else
  156. #
  157. #      Else ::= else Statement | ""
  158. #
  159. #      While ::= while Expr do Statement
  160. #
  161. #      Expr ::= R | Id := Expr 
  162. #
  163. #      R ::= X [=,!=,<,>,>=,<=] X | X
  164. #
  165. #      X ::= T [+-] X | T
  166. #
  167. #      T ::= F [*/%] T | F
  168. #
  169. #      F ::= E ^ F | E
  170. #
  171. #      E ::= L | [+,-] L
  172. #
  173. #      L ::= Func | Id | Constant | ( Expr ) | String
  174. #
  175. #      Func ::= Id ( Arglist )
  176. #
  177. #      Arglist ::= "" | Expr | Expr , arglist
  178.  
  179. #
  180. #  Note, this version correctly handles left-associativity
  181. #    despite the fact that the above grammar doesn't
  182. #    handle it correctly.  (Cannot embed left-associativity
  183. #    into a recursive descent parser!)
  184. #
  185.  
  186. procedure parse(s)        # must match entire line
  187.    local tree
  188.  
  189.    if s ? ((tree := Statement()) & (ws(),pos(0))) then {
  190.       return tree
  191.       }
  192.    write("Syntax error.")
  193. end
  194.  
  195. procedure Statement()
  196.    suspend If() | While() | Compound() | Expr()
  197. end
  198.  
  199. procedure Compound()
  200.    suspend unary("{",2(litmat("{"),Statement_list(),litmat("}")))
  201. end
  202.  
  203. procedure Statement_list()
  204.    local t
  205.    t := scan()
  206.    suspend binary(save(Statement,t), litmat(";"), Statement_list()) | restore(t)
  207. end
  208.  
  209. procedure If()
  210.    suspend trinary(keymat("if"),Expr(),2(keymat("then"),Statement()),
  211.                                        2(keymat("else"),Statement())|&null)
  212. end
  213.  
  214. procedure While()
  215.    suspend binary(2(keymat("while"),Expr()),"while",2(keymat("do"),Statement()))
  216. end
  217.  
  218. procedure Expr()
  219.    suspend binary(Id(),litmat(":="),Expr()) | R()
  220. end
  221.  
  222. procedure R()
  223.    local t
  224.    t := scan()
  225.    suspend binary(save(X,t),litmat(!["=","!=","<=",">=","<",">"]),X()) |
  226.            restore(t)
  227. end
  228.    
  229. procedure X()
  230.    local t
  231.    t := scan()
  232.    suspend binary(save(T,t),litmat(!"+-"),X()) | restore(t)
  233. end
  234.  
  235. procedure T()
  236.    local t
  237.    t := scan()
  238.    suspend binary(save(F,t),litmat(!"*/%"),T()) | restore(t)
  239. end
  240.  
  241. procedure F()
  242.    local t
  243.    t := scan()
  244.    suspend binary(save(E,t),litmat("^"),F()) | restore(t)
  245. end
  246.  
  247. procedure E()
  248.    suspend unary(litmat(!"+-"),L()) | L()
  249. end
  250.  
  251. procedure L()
  252.    # keep track of fact expression was parenthesized,
  253.    #   so we don't accidently override the parens when
  254.    #   handling left-associativity
  255.    suspend Func() | Id() | Const() |
  256.            unary("(",2(litmat("("), Expr(), litmat(")"))) |
  257.            String()
  258. end
  259.  
  260. procedure Func()
  261.    suspend binary(Id(),litmat("("),1(Arglist(),litmat(")")))
  262. end
  263.  
  264. procedure Arglist()
  265.    local a
  266.    a := []
  267.    suspend (a <- ([Expr()] | [Expr()] ||| 2(litmat(","),Arglist()))) | a
  268. end
  269.  
  270. procedure Id()
  271.    static first, rest
  272.  
  273.    initial {
  274.       first := &letters ++ "_"
  275.       rest := first ++ &digits
  276.       }
  277.  
  278.    suspend 2(ws(),id(tab(any(first))||tab(many(rest)) | tab(any(first))))
  279. end
  280.  
  281. procedure Const()
  282.    local t
  283.  
  284.    t := scan()
  285.  
  286.    suspend 2(ws(),const((save(digitseq,t)||="."||digitseq()) | restore(t)))
  287.  
  288. end
  289.  
  290. procedure digitseq()
  291.    suspend tab(many(&digits))
  292. end
  293.  
  294. procedure String()
  295.     # can be MUCH smarter, see calc.icn (by Ralph Griswold) for
  296.     #   example of how to do so...
  297.     suspend 2(litmat("\""),tab(upto('"')),move(1))
  298. end
  299.  
  300. procedure litmat(s)
  301.    suspend 2(ws(),=s)
  302. end
  303.  
  304. procedure keymat(key)
  305.    suspend 2(ws(),key==tab(many(&letters)))
  306. end
  307.  
  308. procedure ws()
  309.    static wsp
  310.    initial wsp := ' \t'
  311.    suspend ""|tab(many(wsp))
  312. end
  313.  
  314. procedure binary(l,o,r)
  315.    local lm
  316.  
  317.    # if operator is left-associative, then alter tree to
  318.    #    reflect that fact, since it isn't parsed that way
  319.    # (this isn't the most efficient way to do this, but
  320.    #  it is a simple way...)
  321.  
  322.    if (type(r) == "binop") & samelop(o,r.op) then {
  323.  
  324.       # ok, have to add node to far left end of chain for r
  325.  
  326.       # ...do so by first finding leftmost node of chain for r
  327.       lm := r
  328.       while (type(lm.left) == "binop") & samelop(o,lm.left.op) do {
  329.          lm := lm.left
  330.          }
  331.  
  332.       # ...add new node as new left-most node in chain
  333.       lm.left := binop(o,l,lm.left)
  334.  
  335.       # ...and return original right child as root of tower
  336.       return r
  337.       }
  338.  
  339.    # nothing to do, just return 'normal' tree
  340.    return binop(o,l,r)
  341. end
  342.  
  343. procedure samelop(o1,o2)
  344.    # both operators are left associative at the same precedence level
  345.    return (any('+-',o1) & any('+-',o2)) |
  346.           (any('*/%',o1) & any('*/%',o2))
  347. end
  348.  
  349. ## Speed up tools for recursive descent parsing...
  350. #
  351. #     The following two routines make it possible to 'defer'
  352. #        the backtracking into a parsing procedure (at least
  353. #        so far as restoring &pos).  This makes it easy to
  354. #        reuse the result of a parsing procedure if needed.
  355. #
  356. #     For example,  the grammar rules:
  357. #
  358. #    X := T | T + F
  359. #
  360. #     can be processed as:
  361. #
  362. #    X := save(T,t) | restore(t) + F
  363. #
  364. #     The net effect is a very substantial speedup in processing
  365. #     such rules.
  366. #
  367.  
  368. record scan(val,pos)    # used to avoid repeating a successful scan
  369.             #   (see the use of save() and restore())
  370.  
  371. # save the current scanning position and result of parsing procedure P
  372. #   and then prevent backtracking into P
  373. #
  374. procedure save(P,t)
  375.    return (t.pos <- &pos, t.val := P())
  376. end
  377.  
  378. #
  379. # if t has in it the saved result of a parsing procedure, then
  380. #   suspend it.  if backtracked into reset position back to
  381. #   start of original call to that parsing procedure.
  382. #
  383. procedure restore(t)
  384.    suspend \t.val
  385.    &pos := \t.pos
  386. end
  387.  
  388. ### execution of infix expression...
  389.  
  390. ## process -- given an expression tree - walk it to produce a result
  391. #
  392.  
  393.     # The only tricky part is in the assignment operator.
  394.     # Here, since we know the left-hand side is an identifier
  395.     # We avoid processing it, since process(id(name)) will
  396.     # return the value of id(name), not it's address.
  397.     
  398.     # This version just relies upon the icon interpreter to
  399.     # catch runtime errors.  It would be better to catch them
  400.     # here.
  401.  
  402. procedure process(t)
  403.    local a, val
  404.  
  405.    return case type(t) of {
  406.       "trinary" : case t.op of {    # has to be an 'if'!
  407.                    "if": if process(t.first) ~= 0 then
  408.                             process(t.second)
  409.                          else
  410.                             process(t.third)
  411.                    }
  412.  
  413.       "binop" : case t.op of {
  414.           # the relation operators
  415.            "=" : if process(t.left) = process(t.right) then 1 else 0
  416.            "!=": if process(t.left) ~= process(t.right) then 1 else 0
  417.            "<=": if process(t.left) <= process(t.right) then 1 else 0
  418.            ">=": if process(t.left) >= process(t.right) then 1 else 0
  419.            "<" : if process(t.left) < process(t.right) then 1 else 0
  420.            ">" : if process(t.left) > process(t.right) then 1 else 0
  421.  
  422.           # the arithmetic operators
  423.                    "+" : process(t.left) + process(t.right)
  424.            "-" : process(t.left) - process(t.right)
  425.                    "*" : process(t.left) * process(t.right)
  426.                    "/" : process(t.left) / process(t.right)
  427.                    "%" : process(t.left) % process(t.right)
  428.                    "^" : process(t.left) ^ process(t.right)
  429.  
  430.                   # assignment
  431.                    ":=": sym_tab[t.left.name] := process(t.right)
  432.  
  433.           # statements in a statement list
  434.                    ";" : {
  435.                          process(t.left)
  436.                          process(t.right)
  437.                          }
  438.  
  439.           # while loop
  440.                   "while" : while process(t.left) ~= 0 do
  441.                                process(t.right)
  442.  
  443.           # function calls
  444.                   "("  : t.left.name ! process(t.right)
  445.                   }
  446.  
  447.       "unary" : case t.op of {
  448.                    "-" : -process(t.opnd)
  449.                    "+" : if val := process(t.opnd) then
  450.                             return if val < 0 then -val else val
  451.              # parenthesized expression
  452.                    "(" : process(t.opnd)
  453.                   # compound statement
  454.                    "{" : process(t.opnd)
  455.                    }
  456.  
  457.       "id"    : \sym_tab[t.name] | (write(t.name," is undefined!"),&fail)
  458.  
  459.       "const" : numeric(t.value)
  460.  
  461.       "list"  : {    # argument list for function call
  462.             #   evaluate each argument into a new list
  463.                 a := []
  464.                 every put(a,process(!t))
  465.                 a
  466.                 }
  467.  
  468.       default: t    # anything else (right now, just strings)
  469.       }
  470.  
  471. end
  472.